//STL
#include <iostream>
#include <list>
#include <stdexcept>
#include <time.h>
#include <math.h>

//Graphics
#include "Engine.h"
#include "Drawable.h"
#include "Utilities.h"
#include "Exceptions/GLError.h"
#include "../Billiards.h"

using namespace Graphics;
using namespace Math;

//Class instance
static Engine* _instance=NULL;

/** Protected constructor */
Engine::Engine()
{
	std::cout<< "Initializing the Graphics Rendering Engine..." << std::endl;
	_scene=NULL;
	_fps=0;
	_camera=Camera(Point(1.0f,0.5f,1.0f));

	//Debug enabled?
#ifdef __DEBUG__
	setHelpers(true);
#else
	setHelpers(false);
#endif
}

Engine::~Engine()
{
	std::cout << "Shutting down the Graphics Rendering Engine ... " << std::endl;
	delete _window;
}

/** Returns an instance of the Graphics Engine */
Engine* Engine::instance()
{
	if (!_instance)
		throw new std::logic_error("Engine::initialize() must be called prior to requesting an instance.");

	return _instance;
}

/** GLUT reshape callback */
void Engine::_reshape(int w, int h)
{
	Engine::instance()->_window->setSize(Size((float)w,(float)h));
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40.0f,(GLfloat)w/(GLfloat)h,0.1f,10.0f);
	//glOrtho(-(float)w/2.0,(float)w/2.0,-(float)h/2.0,(float)h/2.0,0.1f,10.0f);

	glMatrixMode(GL_MODELVIEW);

	_instance->_camera.update();
	_instance->_light.resetPosition();
	_instance->_light2.resetPosition();
}

//Calculates FPS
void Engine::_idle()
{
	/*static clock_t lastTime=clock();
	static unsigned long frames=0;
	//Calculate FPS
	clock_t currentTime=clock();
	clock_t elapsed=currentTime-lastTime;

	//Increment frames
	++frames;

	if (elapsed>CLOCKS_PER_SEC)
	{
		_instance->_fps=int((double)frames/(double)elapsed*CLOCKS_PER_SEC);
		lastTime=currentTime;
		frames=0;
	}*/
}

/** GLUT display callback */
void Engine::_display()
{
	//Clear the color and depth buffers
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	//Draw cartesian coordinate system, if in debug mode
	#ifdef __DEBUG__
		if (_instance->_helpers)
			drawCartesianSystem();
	#endif

	if (_instance->_scene)
	{
		//Draw the scene
		_instance->_scene->draw();
	}

	//Show contents
	glutSwapBuffers();

	//Check for errors
	if (GLError::checkErrors())
	{
		std::cout<< (new GLError(""))->message() << std::endl;
		throw new GLError("OpenGL error");
	}
}

/** Starts the graphics engine */
void Engine::initialize(int argc, char* argv[])
{
	glutInit(&argc,argv);
//Tell FreeGLUT we want to cleanup on exit
#ifdef __FREEGLUT_EXT_H__
	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
#endif
	//Enable multisampling to have nice rounded spheres with any jagged lines
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);


	_instance=new Engine();
}

Renderable* Engine::scene()
{
	return _scene;
}

//Creates a new graphics window
void Engine::createWindow(Size size, std::string title, bool fullscreen)
{
	_window=new Window(size, title, fullscreen);
	glutReshapeFunc(_reshape);
	glutDisplayFunc(_display);
	glutKeyboardFunc(_keyboard);
	glutMouseFunc(_mouse);
	glutSpecialFunc(_special);
//Original GLUT did not support the mouse wheel
#ifdef __FREEGLUT_EXT_H__
	glutMouseWheelFunc(_wheel);
#endif
	glutIdleFunc(_idle);

	glClearColor(1.0f,1.0f,1.0f,1.0f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_POLYGON_SMOOTH);
	glEnable(GL_LIGHTING);

	//Setup 1st light
	_light=Light(GL_LIGHT0);
	_light.setPosition(Point(-0.3f,0.4f,-0.3f));

	//Setup 2nd light
	_light2=Light(GL_LIGHT1);
	_light2.setPosition(Point(0.3f,0.3f,0.3f));

	//Separate specular highlights
	glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
}


void Engine::_keyboard(unsigned char key, int x, int y)
{
	if (!_instance || !_instance->_scene)
		return;

	switch(key)
	{
		//D
		case 'd':	_instance->_camera.strafe(Vector(0.0f,0.0f,-0.1f)); 
					_instance->_camera.update(); 
					_instance->_light.resetPosition(); 
					_instance->_light2.resetPosition(); 
					break;
		//A
		case 'a':	_instance->_camera.strafe(Vector(0.0f,0.0f,0.1f));
					_instance->_camera.update(); 
					_instance->_light.resetPosition(); 
					_instance->_light2.resetPosition(); 
					break;
		//W
		case 'w':	_instance->_camera.strafe(Vector(-0.1f,0.0f,0.0f)); 
					_instance->_camera.update(); 
					_instance->_light.resetPosition(); 
					_instance->_light2.resetPosition(); 
					break;
		//S
		case 's':	_instance->_camera.strafe(Vector(0.1f,0.0f,0.0f));
					_instance->_camera.update(); 
					_instance->_light.resetPosition(); 
					_instance->_light2.resetPosition(); 
					break;
		case 32:    Billiards::instance()->start();
		default:std::cout << "Nothing matched" << key <<" . dammit." << std::endl;
	}
	

	//float angle=_instance->_scene->rotation()->angle();
	//if (angle+1>360.0f)
	//	angle=0.0f;
	//_instance->_scene->setRotation(Quaternion(angle+1.0f,Vector::Y));
	//_instance->_scene->setRedraw(true);
	
/*	Drawable::ListT::iterator it;

	Drawable::ListT* c=_instance->_scene->children();

	for (it=c->begin();it!=c->end();it++)
	{
		(*it)->setRotation(Quaternion(angle+2.0f,Vector::X));
		(*it)->setRedraw(true);
	}
*/
	//std::cout<<"Rotation: "<<angle+1 << std::endl;
}

void Engine::_special(int key, int x, int y)
{
	switch (key)
	{
		case GLUT_KEY_DOWN: Billiards::instance()->speed-=3;
							break;
		case GLUT_KEY_UP: Billiards::instance()->speed+=3;  break;
		case GLUT_KEY_LEFT: Billiards::instance()->cue->setX(Billiards::instance()->cue->x()+3.0f);break;
		case GLUT_KEY_RIGHT: Billiards::instance()->cue->setX(Billiards::instance()->cue->x()-3.0f);break;
	}
}

void Engine::_mouse(int button, int state, int x, int y)
{
	if (state==GLUT_UP)
		std::cout << "Mouse click at (" << x << "," << y << ")" << std::endl;
}

void Engine::_wheel(int button, int dir, int x, int y)
{
	float amount=(float)dir*0.02f;

	_instance->_camera.move(Vector(amount,amount,amount));
	_instance->_camera.update();
	_instance->_light.resetPosition();
	_instance->_light2.resetPosition(); 
}

void Engine::start()
{
	std::cout << "Starting ..." << std::endl;

	glutMainLoop();
}

void Engine::shutdown()
{
	delete _instance;
}

/**********
* GETTERS *
**********/
bool Engine::helpers()
{
	return _helpers;
}

void Engine::setHelpers(bool h)
{
	_helpers=h;
}

/**********
* SETTERS *
**********/
void Engine::setScene(Renderable* s)
{
	_scene=s;
}
